import java.awt.AWTKeyStroke;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.text.ParseException;
import java.util.AbstractSet;
import java.util.Set;
import java.util.HashSet;
import java.util.concurrent.ConcurrentSkipListSet;

import javax.swing.BorderFactory;
import javax.swing.JFormattedTextField;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.text.MaskFormatter;

/**
 * This class is the interface between the GUI and the analysis routines and 
 * also houses the data.
 * @author Mike Childers.
 *         Created Aug 9, 2012.
 */
class CellClass implements Comparable<Object> {
	//Static Fields
	private static final String ONE = "One Field";
	private static final String NINE = "Nine Field";
	private static final String NUMBERS = "123456789";
	public static CellSet[] rows = new CellSet[10];
	public static CellSet[] cols = new CellSet[10];
	public static CellSet[] boxes = new CellSet[10];
	// Members
	private int rowgrp;
	private int colgrp;
	private int boxgrp;
	private JPanel parentPane;
	private JPanel onePane;
	private JPanel ninePane;
	private JFormattedTextField[] textPanes = new JFormattedTextField[10];
	private ConcurrentSkipListSet<Integer> possibles;
	private MaskFormatter formatter;
	private CardLayout cl;
	
	/**
	 * Constructor: accepts the parent pane by argument and sets it up as a cardLayout with two cards:
	 * onePane and ninePane. One is for showing the single final answer and the other is to show possibles.
	 *
	 * @param p
	 */
	public CellClass(JPanel p) {
		
		this.parentPane = p;
		this.parentPane.setLayout(new CardLayout());
		this.parentPane.setBackground(Color.white);
		this.parentPane.setBorder(BorderFactory.createLineBorder(Color.black,1));

		this.onePane = new JPanel(new GridLayout(1,0));
		this.ninePane = new JPanel(new GridLayout(3,3));
		this.ninePane.setBackground(Color.white);

		this.cl = (CardLayout)(this.parentPane.getLayout());
		this.parentPane.add(this.onePane,ONE);
		this.parentPane.add(this.ninePane,NINE);
		this.cl.show(this.parentPane, ONE);
		
		this.textPanes[0] = new JFormattedTextField();
		textPanes[0].setHorizontalAlignment(JTextField.CENTER);
		this.textPanes[0].setBackground(Color.white);
		this.setEditable(false);
		Font newTextFieldFont=new Font(textPanes[0].getFont().getName(),Font.BOLD,30);  
		
		textPanes[0].setFont(newTextFieldFont); 
		
		try {
				this.formatter = new MaskFormatter("*");
				this.formatter.setValidCharacters("123456789 ");
				this.formatter.setOverwriteMode(true);
				this.formatter.install(textPanes[0]);
			} catch (ParseException e) {
			// Auto-generated catch block
				e.printStackTrace();
			}
		
		this.onePane.add(this.textPanes[0]);
		this.textPanes[0].setText("");
		// Change the forward focus traversal keys for textPanes[0]
		Set<AWTKeyStroke> forwardKeys = this.textPanes[0].getFocusTraversalKeys(
			    KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
			Set<AWTKeyStroke> newForwardKeys = new HashSet<AWTKeyStroke>(forwardKeys);
			newForwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
			this.textPanes[0].setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
			    newForwardKeys);
		
		class FieldFocusListener implements FocusListener {  
		    public void focusGained(FocusEvent e) {  
		        JFormattedTextField field = (JFormattedTextField) e.getSource();
		        if(field.isEditable())
		        	field.selectAll(); 
		    }  
		    public void focusLost(FocusEvent e) {  
		        //JFormattedTextField field = (JFormattedTextField) e.getSource();    
		    }
		}
		FieldFocusListener focus = new FieldFocusListener();
		this.textPanes[0].addFocusListener(focus);  
		
		for(int i=1;i<=9;i++) {
			this.textPanes[i] = new JFormattedTextField();
			textPanes[i].setHorizontalAlignment(JTextField.CENTER);		
			Font TextFieldFont=new Font(textPanes[i].getFont().getName(),Font.BOLD,10);  
			textPanes[i].setFont(TextFieldFont); 
			
			this.textPanes[i].setBorder(BorderFactory.createEmptyBorder(0,0,1,0));
			this.textPanes[i].setText(Integer.toString(i));
			this.textPanes[i].setBackground(Color.white);
			this.textPanes[i].setEditable(false);
			
			this.ninePane.add(this.textPanes[i]);
			this.possibles = new ConcurrentSkipListSet<Integer>(); 
		}
	}
	
	public void resetPossibles() {
		this.cl.show(parentPane, ONE);
		if(this.textPanes[0].getForeground() == Color.black)
			this.textPanes[0].setText(""); //clear cell if not originally a given value. Reset to start position.
		this.textPanes[0].setForeground(Color.black);
		for(int i=1;i<=9;i++) {
			this.possibles.add(i);
		}
		this.updateTextPanes();
	}
	
	public int compareTo(Object c) {
		int local = this.getRowgrp() *10 + this.getColgrp();
		int remote= ((CellClass)c).getRowgrp() * 10 + ((CellClass)c).getColgrp();
		if(local > remote)
			return 1;
		else if(local < remote)
			return -1;
		else
			return 0;
	}
	
	 /**
	 * Tells this cell to solve itself with the sole value in possibles, if there is only one element left.
	 *
	 * @return boolean true or false indicating whether or not this cell was solved.
	 */
	public boolean Solve() {
		if (this.possibles.size() == 1) {
			int n = this.possibles.iterator().next();
			String s = Integer.toString(n);
			this.textPanes[0].setForeground(Color.black);
			this.textPanes[0].setText(s);
			this.eliminateFromGroups();
			return true;
		} else {
			return false;
		}
	}
	
	public void initialEntry() {
		String s = this.textPanes[0].getText();
		if ( (NUMBERS.contains(s)) ) {
			this.textPanes[0].setForeground(Color.blue);
			this.textPanes[0].setText(s);
			this.eliminateFromGroups();
		}
			
	}
	
	public void initialEntry(int n) {
		String s = Integer.toString(n);
		if ( (NUMBERS.contains(s)) ) {
			this.textPanes[0].setForeground(Color.blue);
			this.textPanes[0].setText(s);
			this.eliminateFromGroups();
		}
			
	}
	
	private void eliminateFromGroups() {
		String s = this.textPanes[0].getText();
		int n = Integer.parseInt(s);
		this.possibles.clear();
		this.updateTextPanes();
		this.cl.show(this.parentPane,CellClass.ONE);
		CellSet[] sets = {CellClass.rows[this.rowgrp], CellClass.cols[this.colgrp], CellClass.boxes[this.boxgrp]};
		for (CellSet group : sets ) {
			for(CellClass i : group) {
				i.removeNumber(n);
			}
		}
	}
	
	public void setEditable(boolean b) {this.textPanes[0].setEditable(b);}
	
	public void setFieldToData() {
		String s = this.textPanes[0].getText();
		if ( (NUMBERS.contains(s)) ) {
			this.initialEntry();
		} else {
			this.cl.show(parentPane, NINE);
		}
	}
	
	/**
	 * Compares set to the set in the current cell.
	 *
	 * @param set
	 * @return 1 if they are equal, 0 if they are not.
	 */
	public int setEquals(AbstractSet<Integer> set) {
		if (this.possibles.equals(set))
			return 1;
		else
			return 0;
	}
	
	/**
	 * returns an array of possibles for this cell.
	 *
	 * @return Collection set of the same length as the size of the possibles set.
	 */
	public ConcurrentSkipListSet<Integer> getSet() {
		return this.possibles;
	}
	
	/**
	 * removes n from the set of possibles if it exists. Does nothing if it doesn't exist.
	 *
	 * @param n
	 */
	public void removeNumber(int n) {
		this.possibles.remove(n);
		this.updateTextPanes();
	}
	
	/**
	 * Updates the nine-grid cell display of possible values fields from the set of possibles.
	 *
	 * 
	 */
	private void updateTextPanes() {
		for(int i=1;i<=9;i++) {
			if (this.possibles.contains(i))
				this.textPanes[i].setVisible(true);
			else
				this.textPanes[i].setVisible(false);
		}
	}
	// Some obvious sets and gets.
	public void setRowgrp(int r) { this.rowgrp = r;}
	
	public void setColgrp(int c) { this.colgrp = c;}
	
	public void setBoxgrp(int b) { this.boxgrp = b;}
	
	public int getRowgrp() {return this.rowgrp;}
	
	public int getColgrp() {return this.colgrp;}
	
	public int getBoxgrp() {return this.boxgrp;}
	
	public void requestFocus() {this.textPanes[0].requestFocusInWindow();}
	
	public boolean isEmpty() {return this.possibles.isEmpty();}
	
	
	
	/**
	 * 
	 *
	 * @return a String indicating this Cell's row and column plus its solved value or list of possibles. 
	 * (i.e. ([R1C1]::[1,2,3]) or ([R1C1]:1:) )
	 */
	public String toString() {
		String result = "";
		result = "([R"+String.valueOf(this.rowgrp) +"C"+String.valueOf(this.colgrp)+"]:"+this.textPanes[0].getText()+":"+this.possibles+")";
		return result;
	}
	
	public JFormattedTextField getCellPanes() {
		return this.textPanes[0];
	}
	
	public boolean isSameCell(CellClass c) {
		if((this.getRowgrp() == c.getRowgrp()) && (this.getColgrp() == c.getColgrp())) {
			return true;
		} else {
			return false;
		}
	}
	
	public boolean equals(CellClass c) {
		String s1 = this.textPanes[0].getText();
		String s2 = c.textPanes[0].getText();
		if(!(s1.isEmpty()) && (s1.equals(s2) && !(this.isSameCell(c)) )) {
			if(NUMBERS.contains(s1)){
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
	
	public void removeAll(ConcurrentSkipListSet<Integer> set) {
		this.possibles.removeAll(set);
	}
}

